#include <modules\graphics\opengl\opengl_graphics_interface.h>

namespace DelGE
{
	namespace Modules
	{
		namespace Graphics
		{
			OpenGLGraphicsInterface::OpenGLGraphicsInterface()
			{
				this->sName = "OpenGL Graphics Interface";
			}

			OpenGLGraphicsInterface::~OpenGLGraphicsInterface()
			{
				this->sName = "";
			}

			GraphicsContext* OpenGLGraphicsInterface::CreateContext(SystemGadget* pGadget)
			{
				GraphicsContext* pGraphicsContext;

				pGraphicsContext = new OpenGLGraphicsContext(pGadget);
				this->AddContext(pGraphicsContext);
				this->SetCurrentContext(pGraphicsContext);

				return pGraphicsContext;
			}

			#if defined(DELGE_PLATFORM_WIN32)
			GraphicsContext* OpenGLGraphicsInterface::CreateContext(HWND hWnd)
			{
				GraphicsContext* pGraphicsContext;

				pGraphicsContext = new OpenGLGraphicsContext(hWnd);
				this->AddContext(pGraphicsContext);
				this->SetCurrentContext(pGraphicsContext);

				return pGraphicsContext;
			}
			#endif

			void OpenGLGraphicsInterface::Setup2DProjection(int iWidth, int iHeight)
			{
				if (iWidth <= -1) iWidth = this->pCurrentContext->GetWidth();
				if (iHeight <= -1) iHeight = this->pCurrentContext->GetHeight();
				
				this->SetProjectionMatrix(Matrix::CreateOrthographicsProjection(0.0f, (float)iWidth,
																				(float)iHeight, 0.0f,
																				1.0f, -1.0f));
				this->SetModelViewMatrix(Matrix::Identity());

				glViewport(0, 0, iWidth, iHeight);
			}

			void OpenGLGraphicsInterface::SetViewport(int iX, int iY, int iWidth, int iHeight)
			{
				// if we are setting the viewport the screen size then just disable the test.
				if ( (iX == 0) && (iY == 0) && (iWidth == this->pCurrentContext->GetWidth()) && iHeight == this->pCurrentContext->GetHeight() )
					glDisable(GL_SCISSOR_TEST);
				else
				{
					glEnable(GL_SCISSOR_TEST);
					if (this->pCurrentRenderBuffer == NULL)
						glScissor(iX, this->pCurrentContext->GetHeight() - iY - iHeight, iWidth, iHeight);
					else
						glScissor(iX, iY, iWidth, iHeight);
				}
			}

			void OpenGLGraphicsInterface::SetClearColor(int iRed, int iGreen, int iBlue, int iAlpha)
			{
				this->oClearColor = Color(iRed, iGreen, iBlue, iAlpha);

				glClearColor(this->oClearColor.GetRedScalar(),
						  this->oClearColor.GetGreenScalar(),
						  this->oClearColor.GetBlueScalar(),
						  this->oClearColor.GetAlphaScalar());
			}

			void OpenGLGraphicsInterface::SetClearColor(const Color& rColor)
			{
				this->oClearColor = rColor;

				glClearColor(this->oClearColor.GetRedScalar(),
						  this->oClearColor.GetGreenScalar(),
						  this->oClearColor.GetBlueScalar(),
						  this->oClearColor.GetAlphaScalar());
			}

			void OpenGLGraphicsInterface::SetGlobalColor(int iRed, int iGreen, int iBlue, int iAlpha)
			{
				this->oGlobalColor = Color(iRed, iGreen, iBlue, iAlpha);

				glColor4f(this->oGlobalColor.GetRedScalar(),
						  this->oGlobalColor.GetGreenScalar(),
						  this->oGlobalColor.GetBlueScalar(),
						  this->oGlobalColor.GetAlphaScalar());
			}

			void OpenGLGraphicsInterface::SetGlobalColor(const Color& rColor)
			{
				this->oGlobalColor = rColor;

				glColor4f(this->oGlobalColor.GetRedScalar(),
						  this->oGlobalColor.GetGreenScalar(),
						  this->oGlobalColor.GetBlueScalar(),
						  this->oGlobalColor.GetAlphaScalar());
			}

			void OpenGLGraphicsInterface::ClearScreen()
			{
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			}

			void OpenGLGraphicsInterface::Flip()
			{
				this->SetViewport(0, 0, this->pCurrentContext->GetWidth(), this->pCurrentContext->GetHeight());
				this->pCurrentContext->SwapBuffers();
			}

			void OpenGLGraphicsInterface::SetVsync(bool bVsync)
			{
				#if defined(DELGE_PLATFORM_WIN32)
				typedef BOOL (APIENTRY * WGLSWAPINTERVALEXT) (int);
				WGLSWAPINTERVALEXT wglSwapIntervalEXT=(WGLSWAPINTERVALEXT)wglGetProcAddress("wglSwapIntervalEXT");
				if(wglSwapIntervalEXT) wglSwapIntervalEXT(bVsync);
				#endif
			}

			void OpenGLGraphicsInterface::SetBlendMode(DelGEGraphicsBlendMode eBlendMode)
			{
				this->eBlendMode = eBlendMode;

				switch (this->eBlendMode)
				{
				case DelGEGraphicsBlendMode_Solid:
					glDisable(GL_BLEND);
					glDisable(GL_ALPHA_TEST);
					break;

				case DelGEGraphicsBlendMode_Alpha:
					glEnable(GL_BLEND);
					glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
					glDisable(GL_ALPHA_TEST);
					break;

				case DelGEGraphicsBlendMode_Light:
					glEnable(GL_BLEND);
					glBlendFunc(GL_SRC_ALPHA, GL_ONE);
					glDisable(GL_ALPHA_TEST);
					break;

				case DelGEGraphicsBlendMode_Dark:
					glEnable(GL_BLEND);
					glBlendFunc(GL_DST_COLOR, GL_ZERO);
					glDisable(GL_ALPHA_TEST);
					break;
				}
			}

			Texture* OpenGLGraphicsInterface::LoadTexture(string sFilepath, 
																  const Color& oMaskColor, 
																  GraphicsTexture_UVWrapMode eUWrapMode,
															      GraphicsTexture_UVWrapMode eVWrapMode)
			{
				return new OpenGLTexture(sFilepath, oMaskColor, eUWrapMode, eVWrapMode);
			}

			Texture* OpenGLGraphicsInterface::CreateTexture(int iWidth, int iHeight,
																	GraphicsTexture_UVWrapMode eUWrapMode,
																	GraphicsTexture_UVWrapMode eVWrapMode)
			{
				return new OpenGLTexture(iWidth, iHeight, eUWrapMode, eVWrapMode);
			}

			Font* OpenGLGraphicsInterface::LoadFont(string sFilepath, int iSize)
			{
				return new OpenGLFont(sFilepath, iSize);
			}

			void OpenGLGraphicsInterface::UnloadFont(Font* pFont)
			{
				delete (OpenGLFont*)pFont;
			}

			void OpenGLGraphicsInterface::BeginRenderToTexture(Texture* pGraphicsTexture)
			{
				this->pCurrentRenderBuffer = pGraphicsTexture;


				this->SetProjectionMatrix(Matrix::CreateOrthographicsProjection(0.0f, (float)this->pCurrentContext->GetWidth(), 
																				0.0f, (float)this->pCurrentContext->GetHeight(), 
																				1.0f, -1.0f));
				this->SetModelViewMatrix(Matrix::Identity());

				glViewport(0, 0, pGraphicsTexture->GetWidth(), pGraphicsTexture->GetHeight());
			}

			void OpenGLGraphicsInterface::EndRenderToTexture()
			{
				this->BindTexture(this->pCurrentRenderBuffer);

				glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, this->pCurrentRenderBuffer->GetWidthPow2(), this->pCurrentRenderBuffer->GetHeightPow2());

				this->SetModelViewMatrix(Matrix::Identity());

				glViewport(0, 0, this->pCurrentContext->GetWidth(), this->pCurrentContext->GetHeight());

				this->BindTexture(NULL);

				this->pCurrentRenderBuffer = NULL;
			}

			void OpenGLGraphicsInterface::DrawRect(float fX, float fY, float fWidth, float fHeight)
			{
				glBegin(GL_TRIANGLES);

				glVertex2f(fX, fY);
				glVertex2f(fX + fWidth, fY);
				glVertex2f(fX, fY + fHeight);
				glVertex2f(fX + fWidth, fY);
				glVertex2f(fX + fWidth, fY + fHeight);
				glVertex2f(fX, fY + fHeight);

				glEnd();
			}

			void OpenGLGraphicsInterface::DrawLine(float fX0, float fY0, float fX1, float fY1)
			{
				glBegin(GL_LINES);

				glVertex2f(fX0, fY0);
				glVertex2f(fX1, fY1);

				glEnd();
			}

			void OpenGLGraphicsInterface::DrawTexture(Texture* pTexture, float fX, float fY, float fWidth, float fHeight)
			{
				this->DrawTextureArea(pTexture, fX, fY, fWidth, fHeight);
			}

			void OpenGLGraphicsInterface::DrawTextureArea(Texture* pTexture, float fX, float fY, float fWidth, float fHeight,
														  int iAreaX, int iAreaY, int iAreaWidth, int iAreaHeight)
			{
				float fStartU, fStartV;
				float fEndU, fEndV;

				fStartU = 0;
				fStartV = 0;
				fEndU = pTexture->GetUOffset();
				fEndV = pTexture->GetVOffset();

				if (fWidth <= -1) fWidth = (float)pTexture->GetWidth();
				if (fHeight <= -1) fHeight = (float)pTexture->GetHeight();

				if (iAreaX > -1) fStartU = ((float)iAreaX / (float)pTexture->GetWidthPow2());
				if (iAreaY > -1) fStartV = ((float)iAreaY / (float)pTexture->GetHeightPow2());
				if (iAreaWidth > -1) fEndU = ((float)(iAreaX + iAreaWidth) / (float)pTexture->GetWidthPow2());
				if (iAreaHeight > -1) fEndV = ((float)(iAreaY + iAreaHeight) / (float)pTexture->GetHeightPow2());

				Mesh quadMesh;

				quadMesh.AddVertex(fX,			fY,			 0,	fStartU,	fStartV,	this->oGlobalColor);
				quadMesh.AddVertex(fX + fWidth,	fY,			 0,	fEndU,		fStartV,	this->oGlobalColor);
				quadMesh.AddVertex(fX,			fY + fHeight,0,	fStartU,	fEndV,		this->oGlobalColor);
				quadMesh.AddVertex(fX +fWidth,	fY + fHeight,0,	fEndU,		fEndV,		this->oGlobalColor);

				quadMesh.AddIndexedTriangle(0, 1, 2);
				quadMesh.AddIndexedTriangle(1, 3, 2);

				quadMesh.BuildMesh();

				glEnableClientState(GL_VERTEX_ARRAY);
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
				glEnableClientState(GL_COLOR_ARRAY);

				glVertexPointer(3, GL_FLOAT, 0, quadMesh.GetVertexArray());
				glTexCoordPointer(2, GL_FLOAT, 0, quadMesh.GetTextureCoordArray());
				glColorPointer(4, GL_FLOAT, 0, quadMesh.GetColorArray());

				glPushAttrib(GL_ENABLE_BIT);

				glEnable(GL_TEXTURE_2D);
				glDisable(GL_LIGHTING);

				this->BindTexture(pTexture);

				glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, quadMesh.GetIndexArray());

				this->BindTexture(NULL);

				glPopAttrib();

				glDisableClientState(GL_VERTEX_ARRAY);
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);
				glDisableClientState(GL_COLOR_ARRAY);
			}

			void OpenGLGraphicsInterface::DrawMesh(Mesh* pMesh, bool bAutoBuild)
			{
				if (pMesh == NULL) return;

				if ((pMesh->NeedsRebuilding() == true) && (bAutoBuild == true))
					pMesh->BuildMesh();
				else if (bAutoBuild == false)
					return; // Cant drawn a broken mesh.

				glEnableClientState(GL_VERTEX_ARRAY);
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);
				glEnableClientState(GL_COLOR_ARRAY);
				glEnableClientState(GL_NORMAL_ARRAY);

				glVertexPointer(3, GL_FLOAT, 0, pMesh->GetVertexArray());
				glTexCoordPointer(2, GL_FLOAT, 0, pMesh->GetTextureCoordArray());
				glColorPointer(4, GL_FLOAT, 0, pMesh->GetColorArray());
				glNormalPointer(GL_FLOAT, 0, pMesh->GetNormalArray());

				glDrawElements(GL_TRIANGLES, pMesh->GetIndexedTriangleCount() * 3, GL_UNSIGNED_INT, pMesh->GetIndexArray());

				glDisableClientState(GL_VERTEX_ARRAY);
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);
				glDisableClientState(GL_COLOR_ARRAY);
				glDisableClientState(GL_NORMAL_ARRAY);
			}

			void OpenGLGraphicsInterface::DrawModel(Model* pModel)
			{
				this->BindTexture(pModel->GetTexture());
				this->DrawMesh(pModel->GetMesh());
				this->BindTexture(NULL);
			}

			void OpenGLGraphicsInterface::DrawEntity(Entity* pEntity)
			{
				if (pEntity->IsHidden()) return;

				this->PushWorldMatrix();
				
				this->SetModelViewMatrix(this->GetModelViewMatrix() * pEntity->GetLocalMatrix());

				for (int iModelIndex = 0; iModelIndex < pEntity->GetModelCount(); iModelIndex ++)
					this->DrawModel(pEntity->GetRenderModel(iModelIndex));

				this->PopWorldMatrix();
			}

			void OpenGLGraphicsInterface::DrawCamera(Camera* pCamera)
			{
				this->Setup2DProjection();
				this->SetGlobalColor(pCamera->GetBackgroundColor());
				this->DrawRect((float)pCamera->GetViewportX(), (float)pCamera->GetViewportY(), (float)pCamera->GetViewportWidth(), (float)pCamera->GetViewportHeight());
				glClear(GL_DEPTH_BUFFER_BIT);

				this->SetProjectionMatrix(pCamera->GetProjectionMatrix());

				this->SetModelViewMatrix(Matrix::Identity());

				glViewport(pCamera->GetViewportX(), (this->pCurrentContext->GetHeight() - pCamera->GetViewportHeight()) - pCamera->GetViewportY(), 
						   pCamera->GetViewportWidth(), pCamera->GetViewportHeight());
				this->SetViewport(pCamera->GetViewportX(), pCamera->GetViewportY(), 
								  pCamera->GetViewportWidth(), pCamera->GetViewportHeight());

				this->SetModelViewMatrix(pCamera->GetLocalMatrix().Inverse());

				this->SetGlobalColor(Color(255, 255, 255, 255));
			}

			void OpenGLGraphicsInterface::DrawSprite(Sprite* pSprite)
			{
				DelGEGraphicsBlendMode eOldBlendMode = this->eBlendMode;

				this->SetBlendMode(pSprite->GetBlendMode());

				Matrix currentMatrix = this->oModelViewMatrix;
				currentMatrix = currentMatrix * pSprite->GetLocalMatrix();

				this->SetModelViewMatrix(currentMatrix);

				glPushAttrib(GL_ENABLE_BIT);

				glEnable(GL_TEXTURE_2D);
				glDisable(GL_LIGHTING);

				this->BindTexture(pSprite->GetTexture());

				this->DrawMesh(pSprite->GetMesh());

				glPopAttrib();

				this->SetBlendMode(eOldBlendMode);
				this->BindTexture(NULL);

				vector<Sprite*> childVector = pSprite->GetChildren();
				if (!childVector.empty())
				{
					for (unsigned int i = 0; i < childVector.size(); i ++)
						this->DrawSprite(childVector[i]);
				}
			}

			void OpenGLGraphicsInterface::DrawText(string sText, float fX, float fY, Font* pFont)
			{
				float fDx, fDy;
				float fRectX, fRectY, fRectWidth, fRectHeight;
				float fU, fV;
				fDx = fDy = fRectX = fRectY = fRectWidth = fRectHeight = 0;

				fDx = fX;
				fDy = fY + pFont->GetSize();

				glPushAttrib(GL_ENABLE_BIT);

				glEnable(GL_TEXTURE_2D);
				glDisable(GL_LIGHTING);

				for (unsigned int i = 0; i < sText.length(); i++)
				{
					glBindTexture(GL_TEXTURE_2D, ((OpenGLFont*)pFont)->GetOpenGLTexture(sText[i]));

					fRectX		= fDx + pFont->GetCharacterOffset(sText[i])[0];
					fRectY		= fDy - pFont->GetCharacterOffset(sText[i])[1];
					fRectWidth	= (float)pFont->GetBitmapSize(sText[i])[0];
					fRectHeight	= (float)pFont->GetBitmapSize(sText[i])[1];
					fU			= pFont->GetCharacterUV(sText[i])[0];
					fV			= pFont->GetCharacterUV(sText[i])[1];
					
					glBegin(GL_TRIANGLE_STRIP);

					glTexCoord2f(0,		0);		glVertex2f(fRectX,					fRectY);
					glTexCoord2f(fU,	0);		glVertex2f(fRectX + fRectWidth,		fRectY);
					glTexCoord2f(0,		fV);	glVertex2f(fRectX,					fRectY + fRectHeight);
					glTexCoord2f(fU,	fV);	glVertex2f(fRectX + fRectWidth,		fRectY + fRectHeight);

					glEnd();

					fDx += pFont->GetCharacterSize(sText[i])[0];
				}

				glPopAttrib();

				glBindTexture(GL_TEXTURE_2D, 0);
			}

			float OpenGLGraphicsInterface::TextWidth(string sText, Font* pFont)
			{
				if (sText == "") return 0;

				float width;

				width = 0.0f;

				for (unsigned int i = 0; i < sText.length(); i++)				
					width += pFont->GetCharacterSize(sText[i])[0];

				return width;
			}

			float OpenGLGraphicsInterface::TextHeight(string sText, Font* pFont)
			{
				if (sText == "") return 0;

				float height;

				height = (float)pFont->GetBitmapSize(sText[0])[1];
				for (unsigned int i = 0; i < sText.length(); i++)
				{
					if (pFont->GetBitmapSize(sText[i])[1] > height)
						height = (float)pFont->GetBitmapSize(sText[i])[1];
				}

				return height;
			}

			void OpenGLGraphicsInterface::BindTexture(Texture *pTexture)
			{
				if (pTexture != NULL)
					glBindTexture(GL_TEXTURE_2D, ((OpenGLTexture*)pTexture)->GetGLTextureID());
				else
					glBindTexture(GL_TEXTURE_2D, 0);
			}

			void OpenGLGraphicsInterface::BindShaderEffect(OpenGLShaderEffect* pShaderEffect)
			{
				if (pShaderEffect != NULL)
					glUseProgram(pShaderEffect->GetProgramID());
				else
					glUseProgram(0);
			}

			void OpenGLGraphicsInterface::SetModelViewMatrix(const Matrix& rMatrix)
			{
				this->oModelViewMatrix = rMatrix;
				glMatrixMode(GL_MODELVIEW);
				glLoadMatrixf(this->oModelViewMatrix.fData);
			}

			void OpenGLGraphicsInterface::SetProjectionMatrix(const Matrix& rMatrix)
			{
				this->oProjectionMatrix = rMatrix;
				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				glLoadMatrixf(rMatrix.fData);
			}

			void OpenGLGraphicsInterface::Begin2DRendering()
			{
				glDisable(GL_DEPTH_TEST);
				glDepthMask(GL_FALSE);
				glDisable(GL_LIGHTING);
			}

			void OpenGLGraphicsInterface::Begin3DRendering()
			{
				glEnable(GL_DEPTH_TEST);
				glDepthMask(GL_TRUE);
			}

			void OpenGLGraphicsInterface::EnableWireframeMode()
			{
				glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			}

			void OpenGLGraphicsInterface::EnableDepthTesting()
			{
				glEnable(GL_DEPTH_TEST);
			}

			void OpenGLGraphicsInterface::DisableWireframeMode()
			{
				glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			}

			void OpenGLGraphicsInterface::DisableDepthTesting()
			{
				glDisable(GL_DEPTH_TEST);
			}

			void OpenGLGraphicsInterface::EnableLighting()
			{
				glEnable(GL_LIGHTING);
				glEnable(GL_LIGHT0);
			}

			void OpenGLGraphicsInterface::DisableLighting()
			{
				glDisable(GL_LIGHTING);
				glDisable(GL_LIGHT0);
			}
		}
	}
}